package tech.waterism.algorithm.moea.GA_SEPT;

import lombok.SneakyThrows;
import org.moeaframework.core.PRNG;
import org.moeaframework.core.Solution;
import org.moeaframework.core.variable.EncodingUtils;
import org.moeaframework.core.variable.RealVariable;
import org.moeaframework.problem.AbstractProblem;
import org.springframework.beans.BeanUtils;
import tech.waterism.constant.StatisticsConstants;
import tech.waterism.constant.StringConstants;
import tech.waterism.dto.*;
import tech.waterism.entity.FMCalipararelation;
import tech.waterism.entity.FMCaliparascope;
import tech.waterism.entity.FMParameter;
import tech.waterism.utils.CalibrationUtils;

import java.util.*;
import java.util.stream.Collectors;

public class CaliForecastProblemSept extends AbstractProblem {
    /**
     * 调用dto
     */
    private final CalibrationMoeaDto dto;
    /**
     * 目标/适应度函数名称
     */
    private final String[] objectives;
    /**
     * 参数类型
     */
    private final String[] types;
    /**
     * 参数范围下限
     */
    private final Integer[] lowerBound;
    /**
     * 参数范围上限
     */
    private final Integer[] upperBound;
    /**
     * 参数约束
     */
    private List<FMCalipararelationDto> constraintList;

    /**
     * @param numberOfVariables      模型参数个数
     * @param numberOfObjectives     目标/适应度函数个数
     * @param objectivesForAlgorithm 目标/适应度函数名称
     * @param dto                    预报调用dto
     */
    public CaliForecastProblemSept(int numberOfVariables, int numberOfObjectives, String[] objectivesForAlgorithm,
            CalibrationMoeaDto dto) {
        super(numberOfVariables, numberOfObjectives);
        objectives = objectivesForAlgorithm;
        this.dto = dto;
        constraintList = new LinkedList<>();
        types = new String[numberOfVariables];
        lowerBound = new Integer[numberOfVariables];
        upperBound = new Integer[numberOfVariables];
        double[][] initialVariable = generInitialVariable();
        this.setInitialVariable(initialVariable);
    }

    private double[][] generInitialVariable() {
        double proportion;
        if (dto.getAlgorithm().getDefaultPopulationProportion() != null &&
                dto.getAlgorithm().getDefaultPopulationProportion() > 0 &&
                dto.getAlgorithm().getDefaultPopulationProportion() < 1) {
            proportion = dto.getAlgorithm().getDefaultPopulationProportion();
        } else {
            return null;
        }
        List<String> initList = new LinkedList<>();
        for (FPInputDto fpInput : dto.getFpInputs()) {
            for (FMIndexDto fmIndexDto : fpInput.getMdcdList()) {
                if (fmIndexDto.getMdclb().equals(StatisticsConstants.AUTO_CALIBRATE)) {
                    List<FMParameter> paramList = fmIndexDto.getParameters().stream().filter(i -> i.getNorow() > 3)
                            .collect(Collectors.toList());
                    if (paramList.size() > 0) {
                        paramList = paramList.subList(0, paramList.size() - 1);
                    }
                    // param key->string value
                    Map<String, String> paramMap = paramList.stream().collect(Collectors.toMap(fmParameter -> {
                        String[] paraArr = CalibrationUtils.seperateParam(fmParameter.getParameter());
                        return paraArr[0];
                    }, fmParameter -> {
                        String[] paraArr = CalibrationUtils.seperateParam(fmParameter.getParameter());
                        return paraArr[1];
                    }));
                    if (fmIndexDto.getCaliParascopes() != null) {
                        for (FMCaliparascopeDto caliParascope : fmIndexDto.getCaliParascopes()) {
                            initList.add(paramMap.get(caliParascope.getParam()));
                        }
                    }
                }
            }
        }
        double[] item = new double[initList.size()];
        for (int i = 0; i < initList.size(); i++) {
            item[i] = Double.parseDouble(initList.get(i));
        }
        int populationSize = Integer.parseInt(dto.getAlgorithm().getAttributes().getOrDefault("populationSize", "100"));
        int population = (int) Math.floor(populationSize * proportion);
        double[][] initial = new double[population][item.length];
        for (int i = 0; i < population; i++) {
            for (int j = 0; j < item.length; j++) {
                initial[i][j] = item[j];
            }
        }
        return initial;
    }

    @SneakyThrows
    @Override
    public void evaluate(Solution solution) {
        //数组paras[]是由算法随机生成的一套参数
        double[] paras = EncodingUtils.getReal(solution);
        for (int i = 0; i < paras.length; i++) {
            if (types[i].equals(StringConstants.INT)) {
                paras[i] = PRNG.nextInt(lowerBound[i], upperBound[i]);
                solution.setVariable(i, new RealVariable(paras[i], lowerBound[i], upperBound[i]));
            }
        }
        // 定义目标函数数组
        FitnessCalculationSept fitnessCalculation = new FitnessCalculationSept(dto, paras, types, objectives);
        double[] fitnessCalculationResult;
        // forecast and get evaluation value
        fitnessCalculationResult = fitnessCalculation.getFitness();
        //  punish
        for (FMCalipararelationDto fmCalipararelationDto : constraintList) {
            switch (fmCalipararelationDto.getArithmeticOperator()) {
                case "+":
                    if (fmCalipararelationDto.getComparisonOperator().equals("<") ||
                            fmCalipararelationDto.getComparisonOperator().equals("<=")) {
                        if (paras[fmCalipararelationDto.getLeftOperandId()] +
                                paras[fmCalipararelationDto.getRightOperandId()] >
                                fmCalipararelationDto.getOperateValue()) {
                            for (int j = 0; j < fitnessCalculationResult.length; j++) {
                                fitnessCalculationResult[j] += Double.MAX_VALUE;
                            }
                        }
                    }
                    break;
            }
        }
        solution.setObjectives(fitnessCalculationResult);
    }

    @Override
    public Solution newSolution() {
        Solution solution = new Solution(numberOfVariables, numberOfObjectives);
        int index = 0;
        constraintList = new ArrayList<>();
        // set boundaries
        for (FPInputDto fpInput : dto.getFpInputs()) {
            for (FMIndexDto fmIndexDto : fpInput.getMdcdList()) {
                Map<String, Integer> indexMap = new HashMap<>();
                if (fmIndexDto.getCaliParascopes() != null) {
                    for (FMCaliparascope caliParascope : fmIndexDto.getCaliParascopes()) {
                        switch (caliParascope.getType()) {
                            case StringConstants.INT:
                                solution.setVariable(index, new RealVariable(caliParascope.getLowerbound().intValue(),
                                        caliParascope.getUpperbound().intValue()));
                                lowerBound[index] = caliParascope.getLowerbound().intValue();
                                upperBound[index] = caliParascope.getUpperbound().intValue();
                                break;
                            case StringConstants.DOUBLE:
                                solution.setVariable(index,
                                        new RealVariable(caliParascope.getLowerbound(), caliParascope.getUpperbound()));
                                break;
                        }
                        types[index] = caliParascope.getType();
                        indexMap.put(caliParascope.getParam(), index);
                        index++;
                    }
                }
                if (fmIndexDto.getCaliPararelation() != null) {
                    for (FMCalipararelation fmCalipararelation : fmIndexDto.getCaliPararelation()) {
                        FMCalipararelationDto temp = new FMCalipararelationDto();
                        BeanUtils.copyProperties(fmCalipararelation, temp);
                        temp.setLeftOperandId(indexMap.get(temp.getLeftOperand()));
                        temp.setRightOperandId(indexMap.get(temp.getRightOperand()));
                        constraintList.add(temp);
                    }
                }
            }
        }
        //
        return solution;
    }
}
